/*
 * Created by IntelliJ IDEA.
 * User: amrk
 * Date: 24/01/2004
 * Time: 15:27:31
 */
package com.talios.jira.feeds;

import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.talios.jira.browser.JiraItem;
import com.talios.jira.browser.JiraProjectDetails;
import com.talios.jira.browser.signals.JiraServerDetails;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.input.SAXBuilder;
import org.jdom.xpath.XPath;

import javax.mail.internet.MailDateFormat;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLEncoder;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;

public abstract class AbstractFeedBuilder implements FeedBuilder {
    private static final Logger LOGGER = Logger.getInstance("Jira Browser");
    private Project project;
    protected JiraProjectDetails projectDetails;
    protected JiraServerDetails serverDetails;
    protected JiraServerDetails[] servers;

    /**
     * @jira INTPLUG-4 Support for Multiple Projects
     */
    public abstract String buildRssFeedUrl(boolean includeUserDetails);

    /**
     * @jira INTPLUG-40 Add settings check for invalid project id.
     */
    public Document buildFeed() throws IOException, JDOMException {

        LOGGER.info("Refeshing issues from: " + buildRssFeedUrl(false));

        HttpURLConnection con = (HttpURLConnection) new URL(buildRssFeedUrl(true)).openConnection();
        String feedContent = getFeedContent(con);

        SAXBuilder builder = new SAXBuilder();
        builder.setValidation(false);
        Document doc = builder.build(new StringReader(processUndeclaredEntities(feedContent)));

        return doc;
    }                                        

    private String getFeedContent(HttpURLConnection con) throws IOException {
        BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));

        String inputLine;
        StringBuffer buffer = new StringBuffer();
        while ((inputLine = in.readLine()) != null) {
            buffer.append(inputLine);
            buffer.append('\n');
        }

        in.close();

        return buffer.toString();
    }

    protected String processUndeclaredEntities(String s) {
        String result = s;

        Properties entityProps = new Properties();

        // html40latin.properties stolen from Mozilla FireFox ;-)
        try {
            InputStream resourceAsStream = getClass().getResourceAsStream("html40latin.properties");
            if (resourceAsStream != null) {
                entityProps.load(resourceAsStream);
            } else {
                LOGGER.debug("Unable to load html40latin.properties - entity translation not performed.");
            }
        } catch (IOException e) {
            LOGGER.error("Unable to entity translation table", e);
        }

        Enumeration enumeration = entityProps.keys();
        while (enumeration.hasMoreElements()) {
            String key = (String) enumeration.nextElement();
            String entity = entityProps.getProperty(key).toLowerCase();
            key = key.replaceAll("entity\\.(.*)", "&#$1;");
            result = result.replaceAll(entity, key);
        }

        return result;
    }

    public Set buildFeedData() throws FeedException {
        return buildFeedData(servers);
    }

    public Set buildFeedData(JiraServerDetails[] serverDetails) throws FeedException {
        Set issueSet = new HashSet();
        if (serverDetails != null) {
            for (JiraServerDetails serverDetail : serverDetails) {
                try {
                    Set issues = buildFeedData(serverDetail);
                    issueSet.addAll(issues);
                } catch (FeedException e) {
                    LOGGER.debug("A problem occured gathering issues: " + getMaxDate());
                }
            }
        }
        return issueSet;
    }

    private Set buildFeedData(JiraServerDetails serverDetails) throws FeedException {
        Set issueSet = new HashSet();

        this.serverDetails = serverDetails;

        try {
            if (this instanceof ProjectFeedBuilder) {

                List projectList = serverDetails.getProjectList();
                for (Object aProjectList : projectList) {
                    JiraProjectDetails jiraProjectDetails = (JiraProjectDetails) aProjectList;
                    projectDetails = jiraProjectDetails;

                    Document doc = buildFeed();
                    Set issues = buildFeedData(doc);
                    issueSet.addAll(issues);
                }
            } else {
                Document doc = buildFeed();
                Set issues = buildFeedData(doc);
                issueSet.addAll(issues);

            }
            return issueSet;
        } catch (MalformedURLException e) {
            LOGGER.debug(e.getMessage());
            throw new FeedException(e);
        } catch (IOException e) {
            LOGGER.debug(e.getMessage());
            throw new FeedException(e);
        } catch (JDOMException e) {
            LOGGER.debug(e.getMessage());
            throw new FeedException(e);
        }

    }

    /**
     * @jira INTPLUG-9 Trap bad settings
     * @jira INTPLUG-40 Add settings check for invalid project id.
     */
    private Set buildFeedData(Document doc) throws FeedException {

        Set list = new HashSet();

        try {

            addRssItemsOnPath(doc, "/rss/channel/item", list);
            addRssItemsOnPath(doc, "/rss/item", list);

            String info = "Found " + list.size() + " issues.";
            LOGGER.info(info);

        } catch (JDOMException e) {
            LOGGER.debug(e.getMessage());
            if (e.getMessage().indexOf("link") != -1) {
                throw new FeedException("Invalid username, password, or project id in settings.");
            } else {
                throw new FeedException("Unable to parse RSS file: " + e.getMessage(), e);
            }
        }

        return list;
    }

    private void addRssItemsOnPath(Document doc, String path, Set list) throws JDOMException {
        XPath xpath = XPath.newInstance(path);
        List items = xpath.selectNodes(doc);

        for (Object item1 : items) {
            Element element = (Element) item1;

            JiraItem item = extractJiraItem(element);

            list.add(item);
        }
    }

    /**
     * @jira INTPLUG-4 Support for Multiple Projects
     */
    private JiraItem extractJiraItem(Element element) {

        SimpleDateFormat format;
        JiraServerDetails server = serverDetails;
        if (server.getUseCustomDateFormat()) {
            format = new SimpleDateFormat(server.getCustomDateFormat());
        } else {
            format = new MailDateFormat();
        }

        JiraItem item;

        if (this instanceof ProjectFeedBuilder) {
            item = new JiraItem(projectDetails);
        } else {
            item = new JiraItem(serverDetails);
        }

        item.setKey(element.getChildText("key"));

        String title = element.getChildText("title");
        int pos = title.indexOf("]");
        title = title.substring(pos + 1, title.length());

        item.setTitle(title);
        item.setType(element.getChildText("type"));
        item.setPriority(element.getChildText("priority"));
        item.setStatus(element.getChildText("status"));
        item.setResolution(element.getChildText("resolution"));
        item.setAssignee(element.getChildText("assignee"));
        item.setReporter(element.getChildText("reporter"));
        if (element.getChild("component") != null) {
            item.setComponent(element.getChildText("component"));
        } else {
            item.setComponent("");
        }

        if (element.getChild("version") != null) {
            item.setVersion(element.getChildText("version"));
        } else {
            item.setVersion("");
        }

        if (element.getChild("fixVersion") != null) {
            item.setFixVersion(element.getChildText("fixVersion"));
        } else {
            item.setFixVersion("");
        }

        try {
            if (element.getChildText("created") != null) {
                item.setCreated(format.parse(element.getChildText("created")));
            }
        } catch (ParseException e) {
            LOGGER.error(e.getMessage(), e);
            // TODO Should set to null, and cleanly handle it
            item.setCreated(new Date());
        }
        try {
            if (element.getChildText("updated") != null) {
                item.setUpdated(format.parse(element.getChildText("updated")));
            }
        } catch (ParseException e) {
            LOGGER.error(e.getMessage(), e);
            // TODO Should set to null, and cleanly handle it
            item.setUpdated(new Date());
        }
        try {
            if (element.getChildText("due") != null && !"".equals(element.getChildText("due"))) {
                item.setDueDate(format.parse(element.getChildText("due")));
            } else {

                item.setDueDate(null);
            }
        } catch (ParseException e) {
            LOGGER.error(e.getMessage(), e);
            // TODO Should set to null, and cleanly handle it
            item.setDueDate(new Date());
        }
        item.setDescription(element.getChildText("description"));
        return item;
    }

    public static Date getMaxDate() {
        Calendar cal = Calendar.getInstance();
        cal.set(Calendar.YEAR, 9999);
        cal.set(Calendar.MONTH, 1);
        cal.set(Calendar.DAY_OF_MONTH, 1);
        return cal.getTime();
    }

    /**
     * @jira INTPLUG-4 Support for Multiple Projects
     */
    protected StringBuffer getBaseBuffer(boolean includeUserDetails) {
        StringBuffer buf = new StringBuffer();
        buf.append(serverDetails.getBaseurl());
        buf.append("/secure/IssueNavigator.jspa?view=rss");

        buf.append("&decorator=none");

        addUserDetailsToStringBuffer(includeUserDetails, buf);

        buf.append("&reset=true");
        buf.append("&tempMax=9999"); // max records to return

        return buf;
    }

    protected void addUserDetailsToStringBuffer(boolean includeUserDetails, StringBuffer buf) {
        String username = serverDetails.getUsername();
        try {
            username = URLEncoder.encode(username, "UTF-8");
        } catch (UnsupportedEncodingException e) {
        }

        String password = serverDetails.getPassword();
        try {
            password = URLEncoder.encode(password, "UTF-8");
        } catch (UnsupportedEncodingException e) {
        }

        if (includeUserDetails) {
            buf.append("&os_username=").append(username);
            buf.append("&os_password=").append(password);
        }
    }

    public JiraServerDetails[] getServers() {
        return servers;
    }

    public void setServers(JiraServerDetails[] servers) {
        this.servers = servers;
    }

}